package com.lwq.mall.seckill.service.impl;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.lwq.mall.seckill.dto.SeckillSkuRedisDto;
import com.lwq.mall.seckill.feign.CouponFeignService;
import com.lwq.mall.seckill.feign.ProductFeignService;
import com.lwq.mall.seckill.interceptor.AuthInterceptor;
import com.lwq.mall.seckill.service.SeckillSkuService;
import com.lwq.mall.seckill.vo.SeckillSessionEntity;
import com.lwq.mall.seckill.vo.SeckillSkuRelationEntity;
import com.lwq.mall.seckill.vo.SkuInfoVo;
import com.msb.common.constant.OrderConstant;
import com.msb.common.constant.SeckillConstant;
import com.msb.common.dto.SeckillOrderDto;
import com.msb.common.utils.R;
import com.msb.common.vo.MemberVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Slf4j
@Service("seckillSkuService")
public class SeckillSkuServiceImpl implements SeckillSkuService {
    @Autowired
    CouponFeignService couponFeignService;
    @Autowired
    StringRedisTemplate redisTemplate;
    @Autowired
    ProductFeignService productFeignService;
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    RocketMQTemplate rocketMQTemplate;

    @Override
    public void uploadSeckillSku3day() {
        //1.通过feign 远程调用coupon 获取未来3天的秒杀活动的商品
        R r = couponFeignService.uploadDeskillSku3day();
        if(r.getCode() == 0){
            String json = (String) r.get("data");
             List<SeckillSessionEntity> sessionEntities = JSON.parseArray(json,SeckillSessionEntity.class);
            //2.上架商品 redis 数据保存
            //  2.1缓存每日秒杀的sku信息
            saveSessionInfos(sessionEntities);
            //  2.2缓存每日秒杀的商品信息
            saveSessionSkuInfos(sessionEntities);

        }
    }

    /**
     * 查询出当前时间内的秒杀活动及对应的商品sku信息
     * @return
     */
    @Override
    public List<SeckillSkuRedisDto> getCurrentSeckillSkus() {
        //1.确定当前时间是属于哪个秒杀活动
        long current = DateUtil.current();
        Set<String> keys = redisTemplate.keys(SeckillConstant.SESSION_CACHE_PREFIX + "*");
        if (keys != null) {
            for (String key :keys) {
                String replace = key.replace(SeckillConstant.SESSION_CACHE_PREFIX, "");
                String[] s = replace.split("_");
                long start = Long.parseLong(s[0]);
                long end = Long.parseLong(s[1]);
                if(current > start && current < end){
                    //说明秒杀的活动就是当前时间需要参与的活动
                    List<String> range = redisTemplate.opsForList().range(key, -100, 100);
                    BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SeckillConstant.SKU_CACHE_PREFIX);
                    if (range != null) {
                        List<String> list = ops.multiGet(range);
                        if (list != null) {
                            return list.stream().map(item -> JSON.parseObject(item, SeckillSkuRedisDto.class)).collect(Collectors.toList());
                        }
                    }
                }
            }

        }
        return null;
    }

    /**
     * 根据skuId查询秒杀活动对应的信息
     * @param skuId
     * @return
     */
    @Override
    public SeckillSkuRedisDto getSeckillSessionBySkuId(Long skuId) {
        // 1、找到所有需要参与秒杀的商品的sku信息
        BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SeckillConstant.SKU_CACHE_PREFIX);
        Set<String> keys = ops.keys();
        if(keys != null && !keys.isEmpty()){
            String regx = "\\d_"+skuId;
            for (String key :keys) {
                boolean matches = Pattern.matches(regx, key);
                if(matches){
                    String json = ops.get(key);
                    return JSON.parseObject(json, SeckillSkuRedisDto.class);
                }
            }

        }
        return null;
    }

    @Override
    public String seckill(String killId, String code, int num) {
        //1、根据killId 获取当前秒杀的商品信息，redis中
        BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(SeckillConstant.SKU_CACHE_PREFIX);
        String  json = (String) ops.get("killId");
        if(StringUtils.isNotBlank(json)){
            SeckillSkuRedisDto dto = JSON.parseObject(json, SeckillSkuRedisDto.class);
            //校验合法性 校验时效性
            Long startTime = dto.getStartTime();
            Long endTime = dto.getEndTime();
            long time = new Date().getTime();
            if(time > startTime && time < endTime){
                //说明在秒杀活动时间范围内
                //2、校验 随机和商品 是否合法
                String randCode = dto.getRandCode();
                Long skuId = dto.getSkuId();
                String redisKill = dto.getPromotionSessionId()+"_"+skuId;
                if(randCode.equals(redisKill) && killId.equals(redisKill)){
                    //随机码合法
                    //3、判断抢购商品数量是否合法
                    if(num <= dto.getSeckillLimit().intValue()){
                        //满足限购的条件
                        //4.判断是否满足 幂等性
                        //只要抢购成功就在redis 存储一条信息userId +sessionId + skuId
                        MemberVO memberVO = AuthInterceptor.threadLocal.get();
                        Long userId = memberVO.getId();
                        String redisKey = userId + "_"+ killId;
                        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(redisKey, String.valueOf(num), (endTime - time), TimeUnit.MILLISECONDS);
                        if (Boolean.TRUE.equals(ifAbsent)){
                            //表示数据插入成功 第一次操作
                            RSemaphore semaphore = redissonClient.getSemaphore(SeckillConstant.SKU_STOCK_SEMAPHORE+randCode);
                            try {
                                boolean b = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
                                if(b){
                                    //表示秒杀成功
                                    String orderSn = UUID.randomUUID().toString().replace("_", "");
                                    //继续完成下订单的操作 --> RocketMq
                                    SeckillOrderDto orderDto = new SeckillOrderDto();
                                    orderDto.setOrderSn(orderSn);
                                    orderDto.setSkuId(skuId);
                                    orderDto.setPromotionSessionId(dto.getPromotionSessionId());
                                    orderDto.setNum(num);
                                    orderDto.setSeckillPrice(dto.getSeckillPrice());
                                    orderDto.setMemberId(memberVO.getId());
                                    //RocketMq发送异步消息
                                    rocketMQTemplate.sendOneWay(OrderConstant.ROCKETMQ_SECKILL_ORDER_TOPIC,
                                            JSON.toJSONString(orderDto));
                                    return orderSn;
                                }
                            } catch (InterruptedException e) {
                                log.info("秒杀活动:"+e.getMessage());
                                return null;
                            }


                        }
                    }
                }
            }

        }
        return null;
    }

    /**
     * 缓存sku信息
     * @param sessionEntities
     */
    private void saveSessionSkuInfos(List<SeckillSessionEntity> sessionEntities) {
        sessionEntities.stream().forEach(session ->{
            //循环取出每个活动的session 然后取出对应的skuId，封装相关信息
            BoundHashOperations<String, Object, Object> hashOps = redisTemplate.boundHashOps(SeckillConstant.SKU_CACHE_PREFIX);
            for (SeckillSkuRelationEntity item : session.getRelationEntities()) {
                String skuKey = item.getPromotionSessionId()+"_"+item.getSkuId();
//                item.getSkuId().toString();
                Boolean flag = hashOps.hasKey(skuKey);
                if(!flag){
                    SeckillSkuRedisDto seckillSkuRedisDto = new SeckillSkuRedisDto();
                    //1、获取缓存的sku信息
                    R info = productFeignService.info(item.getSkuId());
                    if (info.getCode() == 0) {
                        //查询成功
                        String json = (String) info.get("skuInfoJson");
                        SkuInfoVo skuInfo =JSON.parseObject(json,SkuInfoVo.class) ;
                        seckillSkuRedisDto.setSkuInfoVo(skuInfo);

                    }
                    //2、获取sku的秒杀信息
                    BeanUtils.copyProperties(item, seckillSkuRedisDto);
                    //3.设置当前商品的秒杀时间
                    seckillSkuRedisDto.setStartTime(session.getStartTime().getTime());
                    seckillSkuRedisDto.setEndTime(session.getEndTime().getTime());
                    //4.随机码
                    String token = UUID.randomUUID().toString().replace("-", "");
                    seckillSkuRedisDto.setRandCode(token);
                    // 绑定对应的 活动编号
                    seckillSkuRedisDto.setPromotionSessionId(item.getPromotionSessionId());
                    //分布式信号量处理 限流
                    RSemaphore semaphore = redissonClient.getSemaphore(SeckillConstant.SKU_STOCK_SEMAPHORE + token);
                    //把秒杀活动的商品数量作为分布式信息号量
                    semaphore.trySetPermits(item.getSeckillCount().intValue());
                    hashOps.put(skuKey, JSON.toJSONString(seckillSkuRedisDto));
                }
            }
        });
    }

    /**
     * 缓存每日秒杀活动信息
     * @param sessionEntities
     */
    private void saveSessionInfos(List<SeckillSessionEntity> sessionEntities) {
        sessionEntities.stream().forEach(item ->{
            //循环每一个活动
            long start = item.getStartTime().getTime();
            long end = item.getEndTime().getTime();
            String key = SeckillConstant.SESSION_CACHE_PREFIX + start + "_" + end;
            Boolean flag = redisTemplate.hasKey(key);
            //表示秒杀活动在redis不存在，还没有上架
            if(!flag){
                //存储Redis中的每个秒杀的商品信息sku
                List<String> collect = item.getRelationEntities().stream().map(relation -> relation.getPromotionSessionId()+"_"+relation.getSkuId().toString()).collect(Collectors.toList());
                redisTemplate.opsForList().leftPushAll(key,collect);
            }
        });

    }
}
